package com.gitee.wsl.model.process

import com.gitee.wsl.model.ModelHook
import com.gitee.wsl.model.NullBody
import com.gitee.wsl.model.OutgoingContent
import com.gitee.wsl.model.process.request.ProcessRequestBuilder
import com.gitee.wsl.model.process.request.RequestPipeline
import com.gitee.wsl.model.process.response.ProcessResponse
import com.gitee.wsl.model.process.response.ReceivePipeline
import com.gitee.wsl.model.process.response.ResponseContainer
import com.gitee.wsl.model.process.response.ResponsePipeline
import com.gitee.wsl.reflect.TypeInfo


/**
 * A context for [ClientPluginBuilder.onRequest] callback.
 */

 class OnRequestContext internal constructor()

/**
 * A context for [ClientPluginBuilder.onResponse] callback.
 */

 class OnResponseContext internal constructor()

/**
 * A context for [ClientPluginBuilder.transformRequestBody] callback.
 */

 class TransformRequestBodyContext internal constructor()

/**
 * A context for [ClientPluginBuilder.transformResponseBody] callback.
 */

 class TransformResponseBodyContext internal constructor()

typealias RequestHookBlock = suspend OnRequestContext.(request: ProcessRequestBuilder, content: Any) -> Unit

internal object RequestHook : ModelHook<ProcessModel,RequestHookBlock> {

    override fun install(
        client: ProcessModel,
        handler: RequestHookBlock
    ) {
        client.requestPipeline.intercept(RequestPipeline.State) {
            handler(OnRequestContext(), context, subject)
        }
    }
}

typealias ResponseHookBlock = suspend OnResponseContext.(processResponse: ProcessResponse) -> Unit

internal object ResponseHook : ModelHook<ProcessModel,ResponseHookBlock> {

    override fun install(
        client: ProcessModel,
        handler: ResponseHookBlock
    ) {
        client.receivePipeline.intercept(ReceivePipeline.State) {
            handler(OnResponseContext(), subject)
        }
    }
}

typealias TransformRequestBodyHookBlock = suspend TransformRequestBodyContext.(
    request: ProcessRequestBuilder,
    content: Any,
    bodyType: TypeInfo?
) -> OutgoingContent?

internal object TransformRequestBodyHook : ModelHook<ProcessModel,TransformRequestBodyHookBlock> {

    override fun install(
        client: ProcessModel,
        handler: TransformRequestBodyHookBlock
    ) {
        client.requestPipeline.intercept(RequestPipeline.Transform) {
            val newContent = handler(TransformRequestBodyContext(), context, subject, context.bodyType)
            if (newContent != null) proceedWith(newContent)
        }
    }
}

typealias TransformResponseBodyHookBlock<CONTENT> = suspend TransformResponseBodyContext.(
    response: ProcessResponse,
    content: CONTENT,
    requestedType: TypeInfo
) -> Any?



internal class TransformResponseBodyHook<CONTENT>(val checkContent:(Any)->CONTENT?) : ModelHook<ProcessModel,TransformResponseBodyHookBlock<CONTENT>> {

    override fun install(
        client:ProcessModel,
        handler: TransformResponseBodyHookBlock<CONTENT>
    ) {
        client.responsePipeline.intercept(ResponsePipeline.Transform) {
            val (typeInfo, content) = subject
            val baseContent = checkContent(content)?:return@intercept
            val newContent = handler(TransformResponseBodyContext(), context.processResponse, baseContent, typeInfo) ?: return@intercept
            if (newContent !is NullBody && !typeInfo.type.isInstance(newContent)) {
                throw IllegalStateException("transformResponseBody returned $newContent but expected value of type $typeInfo")
            }
            proceedWith(ResponseContainer(typeInfo, newContent))
        }
    }
}
